#include "project.h"

// Qt Includes
#include <QtDebug>
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
#include <QTimer>

// Local Includes
#include "filelister.h"
#include "pluginmanager.h"
#include "pluginprocess.h"

// External Includes
#include "plugin.h"
#include "results.h"

// Constants
const quint8 gc_nMaxProcessCount = 5;

/////////////////////////////////////////////////////////////////////
// Construction & Destruction
/////////////////////////////////////////////////////////////////////
CProject::CProject(const QDomDocument &xmlDocument, const QString &strSourcePath, QObject *pParent/* = NULL*/)
	: QObject(pParent),
	  m_strSourcePath(strSourcePath),
	  m_eProjectState(PROJECT_STATE_INIT),
	  m_nRequest(0),
	  m_pFileLister(NULL),
	  m_nCurrentFileIndex(0),
	  m_pResults(NULL)
{
	loadProjectFromXml(xmlDocument);

	// For now, let the project tick itself.
	QTimer *pTimer = new QTimer(this);

	connect(pTimer, SIGNAL(timeout()), this, SLOT(tick()));
	pTimer->setInterval(40);
	pTimer->start();
}

CProject::CProject(QObject *pParent/* = NULL*/)
	: QObject(pParent),
	  m_eProjectState(PROJECT_STATE_INIT),
	  m_nRequest(0),
	  m_pFileLister(NULL),
	  m_nCurrentFileIndex(0),
	  m_pResults(NULL)
{

}

CProject::~CProject()
{
	// Clear out any plugins.
	qDeleteAll(m_lstPlugins);

	delete m_pFileLister;
	delete m_pResults;
}
/////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////
// Member Functions
/////////////////////////////////////////////////////////////////////
bool CProject::loadProjectFromXml(const QDomDocument &xmlDocument)
{
	if(xmlDocument.isNull())
		return false;

	const QDomElement xmlDocumentElement = xmlDocument.documentElement();

	if(xmlDocumentElement.isNull())
		return false;

	if(xmlDocumentElement.tagName() != tr("project"))
		return false;

	// Clear out any existing plugins, in case this is not a new project.
	qDeleteAll(m_lstPlugins);
	m_lstPlugins.clear();

	const QDomNodeList xmlNodeList = xmlDocumentElement.childNodes();
	const int nXmlNodeListCount = xmlNodeList.count();

	for(int i = 0; i < nXmlNodeListCount; i++)
	{
		const QDomNode xmlNode = xmlNodeList.at(i);

		if(xmlNode.isNull())
			continue;

		if(!xmlNode.isElement())
			continue;

		const QDomElement xmlElement = xmlNode.toElement();

		if(xmlElement.isNull())
			continue;

		// What element are we working with?
		if(xmlElement.tagName() == tr("plugin"))
		{
			CPlugin *const pPlugin = new CPlugin;

			if(!pPlugin->loadFromXml(xmlElement))
			{
				delete pPlugin;
				return false;
			}

			m_lstPlugins.append(pPlugin);
			m_slPluginNames.append(pPlugin->getPluginName());
			continue;
		}
	}

	return true;
}

QString CProject::getResultsPath() const
{
	if(!m_pResults)
		return QString();

	return m_pResults->getDBPath();
}

/////////////////////////////////////////////////////////////////////
// Member Functions
/////////////////////////////////////////////////////////////////////
void CProject::setState(tProjectState eProjectState)
{
	m_eProjectState = eProjectState;
}

void CProject::makeRequest(quint8 nRequest)
{
	m_nRequest |= nRequest;
}

bool CProject::isRequestActive(quint8 nRequest, bool bClearRequest/* = true*/)
{
	if((m_nRequest & nRequest) == 0)
		return false;

	if(bClearRequest)
	{
		m_nRequest &= ~nRequest;
	}

	return true;
}

void CProject::addResult(const QString &strOutput)
{
//	qDebug() << strOutput;
	QDomDocument domDocument;

	if(!domDocument.setContent(strOutput))
		return;

	quint32 nInputFileID = 0;
	const QDomNodeList domNodeList = domDocument.documentElement().childNodes();
	const int nNodeListCount = domNodeList.count();

	for(int i = 0; i < nNodeListCount; i++)
	{
		const QDomNode domNode = domNodeList.at(i);

		// We only care about elements.
		if(domNode.nodeType() != QDomNode::ElementNode)
			continue;

		const QDomElement domElement = domNode.toElement();

		// We need to know which input file we were using.
		if(domElement.nodeName() == tr("inputFile"))
		{
			const QString strInputFile = domElement.firstChildElement("name").text().trimmed();
			const QString strInputCode = domElement.firstChildElement("source").text().trimmed();

			QFileInfo fi(strInputFile);

			nInputFileID = m_pResults->addFile(QFileInfo(strInputFile).fileName(), strInputCode);
			m_mapFileNameID[strInputFile] = nInputFileID;
			qDebug() << fi.fileName() << "->" << nInputFileID;

			continue;
		}

		if(domElement.nodeName() == tr("plugin"))
		{
			quint32 nPluginID = 0;
			const QDomNodeList domPluginNodeList = domElement.childNodes();
			const int nPluginNodeListCount = domPluginNodeList.count();

			for(int j = 0; j < nPluginNodeListCount; j++)
			{
				const QDomNode domPluginNode = domPluginNodeList.at(j);

				// We only care about elements.
				if(domPluginNode.nodeType() != QDomNode::ElementNode)
					continue;

				const QDomElement domPluginElement = domPluginNode.toElement();

				if(domPluginElement.nodeName() == tr("name"))
				{
					if(domPluginElement.firstChild().nodeType() == QDomElement::TextNode)
					{
						const QString strPlugin = domPluginElement.firstChild().nodeValue().trimmed();

						nPluginID = m_mapPluginID.value(strPlugin);
						continue;
					}
				}

				if(domPluginElement.nodeName() == tr("results"))
				{
					const QDomNodeList domResultsList = domPluginElement.childNodes();
					const int nResultsNodeListCount = domResultsList.count();

					for(int k = 0; k < nResultsNodeListCount; k++)
					{
						const QDomNode domResultsNode = domResultsList.at(k);

						// We only care about elements.
						if(domResultsNode.nodeType() != QDomNode::ElementNode)
							continue;

						const QDomElement domResultsElement = domResultsNode.toElement();

						if(domResultsElement.nodeName() == tr("result"))
						{
							quint32 nLineStart = 0, nColStart = 0, nLineSpan = 0, nSeverity = 0;
							QString strSyntaxCode;
							const QDomNodeList domResultList = domResultsElement.childNodes();
							const int nResultNodeListCount = domResultList.count();

							for(int l = 0; l < nResultNodeListCount; l++)
							{
								const QDomNode domResultNode = domResultList.at(l);

								// We only care about elements.
								if(domResultNode.nodeType() != QDomNode::ElementNode)
									continue;

								const QDomElement domResultElement = domResultNode.toElement();

								if(domResultElement.nodeName() == tr("lineStart"))
								{
									if(domResultElement.firstChild().nodeType() == QDomElement::TextNode)
									{
										const QString strLineStart =
												domResultElement.firstChild().nodeValue().trimmed();

										nLineStart = strLineStart.toUInt();
									}

									continue;
								}

								if(domResultElement.nodeName() == tr("colStart"))
								{
									if(domResultElement.firstChild().nodeType() == QDomElement::TextNode)
									{
										const QString strColStart =
												domResultElement.firstChild().nodeValue().trimmed();

										nColStart = strColStart.toUInt();
									}

									continue;
								}

								if(domResultElement.nodeName() == tr("lineSpan"))
								{
									if(domResultElement.firstChild().nodeType() == QDomElement::TextNode)
									{
										const QString strLineSpan =
												domResultElement.firstChild().nodeValue().trimmed();

										nLineSpan = strLineSpan.toUInt();
									}

									continue;
								}

								if(domResultElement.nodeName() == tr("severity"))
								{
									if(domResultElement.firstChild().nodeType() == QDomElement::TextNode)
									{
										const QString strSeverity =
												domResultElement.firstChild().nodeValue().trimmed();

										nSeverity = strSeverity.toUInt();
									}

									continue;
								}

								if (domResultElement.nodeName() == "code")
								{
									strSyntaxCode = domResultElement.firstChild().nodeValue().trimmed();
								}
							}

							m_pResults->addResult(nPluginID, nInputFileID, nLineStart, nColStart, nLineSpan, nSeverity, strSyntaxCode);
						}
					}
				}
			}

			continue;
		}
	}
}
/////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////
// Public Slots
/////////////////////////////////////////////////////////////////////
void CProject::startProject()
{
	makeRequest(PROJECT_REQUEST_START);
}

void CProject::tick()
{
	switch(m_eProjectState)
	{
		case PROJECT_STATE_INIT:
		{
			setState(PROJECT_STATE_IDLE);
			break;
		}

		case PROJECT_STATE_IDLE:
		{
			if(isRequestActive(PROJECT_REQUEST_START))
			{
				m_pFileLister = new CFileLister(m_strSourcePath, QStringList() << "*.h" << "*.cpp");

				// TODO: Pass this through the QThreadPool.
				m_pFileLister->run();

				setState(PROJECT_STATE_SCANNING_FILES);
				break;
			}

			break;
		}

		case PROJECT_STATE_SCANNING_FILES:
		{
			if(!m_pFileLister)
			{
				setState(PROJECT_STATE_IDLE);
				break;
			}

			/*
			if(m_pFileLister->isScanning())
				break;
*/

			m_slFilePaths = m_pFileLister->getAbsoluteFilePaths();
			m_pResults = new CResults;

			foreach(CPlugin *pPlugin, m_lstPlugins)
			{
				m_mapPluginID.insert(pPlugin->getPluginName(), m_pResults->addPlugin(pPlugin->getPluginName(),
						pPlugin->getDescription()));
			}

			qDebug() << QString("Beginning project scan. Found %1 files...")
					.arg(m_slFilePaths.count());

			setState(PROJECT_STATE_RUNNING);
			break;
		}

		case PROJECT_STATE_RUNNING:
		{
			// For as long as we're running, we want to be processing source files.
			// If we have space for another process, let's run it up.
			if(m_setRunningProcesses.count() < gc_nMaxProcessCount)
			{
				// Have we run out of files?
				if(m_nCurrentFileIndex >= m_slFilePaths.count())
				{
					qDebug() << "No more files to scan. Waiting for running plugins to complete...";

					// Wait until all plugins finish.
					if(m_setRunningProcesses.count() != 0)
						break;

					// We're done (no more files & no running plugins).
					delete m_pFileLister; m_pFileLister = NULL;
					m_slFilePaths.clear();
					m_nCurrentFileIndex = 0;

					emit projectFinished();
					qDebug() << "Project scan complete.";

					setState(PROJECT_STATE_DONE);
					break;
				}

				QString strFilePath = m_slFilePaths.at(m_nCurrentFileIndex);

				// Run a new plugin instance.
				CPluginManager *pPluginManager = CPluginManager::getPluginManager();
				CPluginProcess *pPluginProcess = pPluginManager->runScannerPlugins(strFilePath, m_slPluginNames,
						QString());

				emit projectProgress(++m_nCurrentFileIndex, m_slFilePaths.count(), tr("Scanning file %1...")
						.arg(strFilePath.remove(m_strSourcePath + "/")));

//				m_mapFileNameID[strFilePath] = m_pResults->addFile(strFilePath);
				m_setRunningProcesses.insert(pPluginProcess);

				qDebug() << QString("Scanning %1...")
						.arg(strFilePath);

				// Signals & Slots.
				connect(pPluginProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this,
						SLOT(pluginFinished(int, QProcess::ExitStatus)));

				if(pPluginProcess->state() == QProcess::NotRunning)
				{
					m_setRunningProcesses.remove(pPluginProcess);
					delete pPluginProcess;
				}
			}

			break;
		}

		case PROJECT_STATE_DONE:
			break;
	}
}
/////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////
// Private Slots
/////////////////////////////////////////////////////////////////////
void CProject::pluginFinished(int nExitCode, QProcess::ExitStatus eExitStatus)
{
	Q_UNUSED(nExitCode);
	Q_UNUSED(eExitStatus);

	CPluginProcess *const pPluginProcess = qobject_cast<CPluginProcess*>(sender());

	if(pPluginProcess)
	{
		const QString strOutput = pPluginProcess->readAllStandardOutput();
		const QString strDebug = pPluginProcess->readAllStandardError();

		qDebug() << strDebug;

		addResult(strOutput);

		m_setRunningProcesses.remove(pPluginProcess);
		delete pPluginProcess;
	}
}
/////////////////////////////////////////////////////////////////////
